import numpy as np
import matplotlib.pyplot as plt
import scipy
import time
import imageio
from PIL import Image
from lr_utils import load_dataset

# 运行load_dataset()函数，接收训练集图片特征m1*64*64*3，
train_set_x_orig, train_set_y, test_set_x_orig, test_set_y, classes = load_dataset()

m_train = train_set_x_orig.shape[0]  # 获取训练样本数 m_train 209
m_test = test_set_x_orig.shape[0]  # 获取测试样本数 m_test 50
num_px = train_set_x_orig.shape[1]  # 图片像素大小 num_px 64

train_set_x_flatten = train_set_x_orig.reshape(m_train, -1).T  # 展平训练样本特征(12288,209)
test_set_x_flatten = test_set_x_orig.reshape(m_test, -1).T  # 展平测试样本特征
train_set_x = train_set_x_flatten / 255.  # 归一化，由于像素值是0-255，所以这里/255
test_set_x = test_set_x_flatten / 255.


# print(train_set_x[0:5,0])


# 激活函数: sigmoid

def sigmoid(z):
    """
    非线性激活，使用的sigmoid函数

    z是w*X.T+b，是对样本特征进行线性变化
    将z输入激活函数，会得到预测值Y_hat
    通过预测值与真值我们可以计算损失函数（代价函数）
    我们的目标就是通过每一轮的训练，优化参数w和b，进而使损失函数降到最低，使预测值接近真实值
    """
    s = 1.0 / (1.0 + np.exp(-1.0 * z))

    return s


# 参数初始化函数

def initialize_with_zeros(dim):
    """
    这个函数的功能是初始化维度为(dim, 1)的零矩阵w，初始化b为0

    参数：
    dim -- 应为样本数

    返回值:
    w -- 维度为(dim, 1)的零矩阵w;
    b -- 实数0
    """
    w = np.zeros((dim, 1))
    b = 0

    assert (w.shape == (dim, 1))
    assert (isinstance(b, float) or isinstance(b, int))  # 确保 b 的类型是 float 或 int

    return w, b


# 传播函数

def propagate(w, b, X, Y):
    """
    函数功能：计算前向传播的预测值和代价函数，计算反向传播的梯度

    参数:
    w -- 权重(num_px * num_px * 3, 1);
    b -- 偏移量，一个实数;
    X -- (num_px * num_px * 3, number of examples);
    Y -- 真值标签矩阵 (containing 0：非猫 1：猫) of size (1, number of examples)。

    返回值:
    cost -- 前向传播的代价函数;
    dw -- 损失函数对于w的梯度，因此形状和w相同（dim,1）;
    db -- 损失函数对于b的梯度。
    """
    m = X.shape[1]

    # 前向传播
    A = sigmoid(np.dot(w.T, X) + b)  # 预测值
    cost = -(1.0 / m) * np.sum(Y * np.log(A) + (1 - Y) * np.log(1 - A))  # 代价函数

    # 反向传播
    dw = (1.0 / m) * np.dot(X, (A - Y).T)  # dl/dw
    db = (1.0 / m) * np.sum(A - Y)  # dl/db

    assert (dw.shape == w.shape)
    assert (db.dtype == float)
    cost = np.squeeze(cost)  # cost原本是一个numpy数组，此步骤将他降为0维实数
    assert (cost.shape == ())

    grads = {"dw": dw,
             "db": db}

    return grads, cost


# 优化参数函数

def optimize(w, b, X, Y, num_iterations, learning_rate, print_cost=False):
    costs = []

    for i in range(num_iterations):
        # 传播函数获取梯度下降
        grads, cost = propagate(w, b, X, Y)
        dw = grads["dw"]
        db = grads["db"]

        # 根据梯度下降公式更新w和b
        w = w - learning_rate * dw
        b = b - learning_rate * db

    # 存储优化后的w和b
    params = {"w": w,
              "b": b}
    # 存储优化后的dw和db
    grads = {"dw": dw,
             "db": db}

    return params, grads, costs


# 预测函数

def predict(w, b, X):
    """
    使用学习到的逻辑回归参数（w和b）预测样本的标签

    参数:
    w -- 权重(num_px * num_px * 3, 1);
    b -- 偏移量, 一个实数;
    X -- 样本矩阵 (num_px * num_px * 3, number of examples)。

    返回值:
    Y_prediction -- 一个包含所有样本预测（0/1）的numpy矩阵。
    """

    m = X.shape[1]  # 样本数目
    Y_prediction = np.zeros((1, m))
    w = w.reshape(X.shape[0], 1)

    # 计算向量 “A” ，预测猫出现在图片中的概率
    A = sigmoid(np.dot(w.T, X) + b)

    for i in range(A.shape[1]):
        # 做阈值处理，若预测指A[0,1]大于0.5，这在预测矩阵Y_prediction[0, i]中填入1，否则填入0
        if A[0, i] > 0.5:
            Y_prediction[0, i] = 1
        else:
            Y_prediction[0, i] = 0

    assert (Y_prediction.shape == (1, m))

    return Y_prediction


# 模型函数

def model(X_train, Y_train, X_test, Y_test, num_iterations=2000, learning_rate=0.5, print_cost=False):
    """
    函数功能：逻辑回归网络的模型

    参数:
    X_train -- 训练集，一个numpy数组(num_px * num_px * 3, m_train);;
    Y_train -- 训练集的真值标签(1, m_train);
    X_test -- 测试集，一个numpy数组(num_px * num_px * 3, m_test)
    Y_test -- 测试集的真值标签(1, m_test)
    num_iterations -- 训练次数
    learning_rate -- 学习率，用于参数更新
    print_cost -- 每100轮打印一次cost

    返回值:
    d -- 一个字典，包含了模型的相关信息
    """

    # 用零初始化参数w和b
    w, b = initialize_with_zeros(X_train.shape[0])

    # 使用梯度下降算法优化参数w和b
    parameters, grads, costs = optimize(w, b, X_train, Y_train, num_iterations, learning_rate, print_cost)

    # 从参数字典获取最优w和b
    w = parameters["w"]
    b = parameters["b"]

    # 对训练集和测试集做预测
    Y_prediction_train = predict(w, b, X_train)
    Y_prediction_test = predict(w, b, X_test)

    # 打印训练和测试的精确率
    print("train accuracy: {} %".format(100 - np.mean(np.abs(Y_prediction_train - Y_train)) * 100))
    print("test accuracy: {} %".format(100 - np.mean(np.abs(Y_prediction_test - Y_test)) * 100))

    d = {"costs": costs,
         "Y_prediction_test": Y_prediction_test,
         "Y_prediction_train": Y_prediction_train,
         "w": w,
         "b": b,
         "learning_rate": learning_rate,
         "num_iterations": num_iterations}

    return d


d = model(train_set_x, train_set_y, test_set_x, test_set_y, num_iterations=1500, learning_rate=0.005, print_cost=True)

# 单一图片示例
index = 1
plt.imshow(test_set_x[:, index].reshape(num_px, num_px, 3))
print("y = " + str(test_set_y[0, index]) + ", you predicted that it is a \"" +
      classes[int(d["Y_prediction_test"][0, index])].decode("utf-8") + "\" picture.")
print("Y_prediction_test:", d["Y_prediction_test"][0, index])
print("classes:", classes[int(d["Y_prediction_test"][0, index])].decode("utf-8"))
# print(d["Y_prediction_test"])
# print(test_set_y)
plt.show()

# 绘制学习曲线（成本）
costs = np.squeeze(d['costs'])
plt.plot(costs)  # costs曲线的横轴是range（length（costs）），纵轴是cost值，matplotlib会自动匹配索引值，实现x和y的对应
plt.ylabel('cost')
plt.xlabel('iterations (per hundreds)')
plt.title("Learning rate =" + str(d["learning_rate"]))
plt.show()

# learning_rates = [0.01, 0.009, 0.008]
# models = {}
# for i in learning_rates:
#     print("learning rate is: " + str(i))
#     models[i] = model(train_set_x, train_set_y, test_set_x, test_set_y,
#                       num_iterations=2000, learning_rate=i, print_cost=False)
#     print('\n' + "-------------------------------------------------------" + '\n')
#
# for i in learning_rates:
#     plt.plot(np.squeeze(models[i]["costs"]), label=str(models[i]["learning_rate"]))
#
# plt.ylabel('cost')
# plt.xlabel('iterations (per hundreds)')
# legend = plt.legend(loc='upper center', shadow=True)
# frame = legend.get_frame()
# frame.set_facecolor('0.90')
# plt.show()

# 放入自己的图片
# my_image = "my_image.jpg"   # change this to the name of your image file
# ## END CODE HERE ##
#
# # We preprocess the image to fit your algorithm.
# fname = "images/" + my_image
# image = np.array(imageio.v2.imread(fname))
# image_resized = np.array(Image.fromarray(image).resize((num_px, num_px)))  # 调整大小
# my_image = image_resized.reshape((1, num_px * num_px * 3)).T               #扁平化处理
# my_predicted_image = predict(d["w"], d["b"], my_image)
#
# plt.imshow(image)
# print("y = " + str(np.squeeze(my_predicted_image)) + "\n" +"your algorithm predicts a \"" + classes[int(np.squeeze(my_predicted_image)),].decode("utf-8") +  "\" picture.")
# plt.show()
